package minio.client;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Part;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;

import static io.minio.http.Method.GET;
import static io.minio.http.Method.PUT;

@AllArgsConstructor
public class MinioTemplate {
    final String BUCKET_NAME;
    final MinioAsyncClientX minioAsyncClient;

    public String upload(String fileName, String fileContentType, String filePath) {
        return upload(fileName, fileContentType, filePath, 0);
    }

    public String upload(String fileName, String fileContentType, String filePath, long partSize) {
        return upload(BUCKET_NAME, fileName, fileContentType, filePath, partSize);
    }

    public String upload(String bucketName, String fileName, String fileContentType, String filePath, long partSize) {
        try {
            boolean bucketExists = minioAsyncClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()).get();
            if (!bucketExists) {
                minioAsyncClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()).get();
            }

            minioAsyncClient.uploadObject(UploadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .filename(filePath, partSize)
                    .contentType(fileContentType)
                    .build()
            ).get();

            return getObjectUrl(bucketName, fileName);
        } catch (InsufficientDataException | IOException | NoSuchAlgorithmException | InvalidKeyException |
                 XmlParserException | InternalException | ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public String upload(String fileName, String fileContentType, InputStream fileStream, long fileSize) {
        return upload(BUCKET_NAME, fileName, fileContentType, fileStream, fileSize);
    }

    public String upload(String bucketName, String fileName, String fileContentType, InputStream fileStream, long fileSize) {
        try {
            boolean bucketExists = minioAsyncClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()).get();
            if (!bucketExists) {
                minioAsyncClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()).get();
            }

            minioAsyncClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(fileStream, fileSize, -1)
                    .contentType(fileContentType)
                    .build()
            ).get();

            return getObjectUrl(bucketName, fileName);
        } catch (InsufficientDataException | IOException | NoSuchAlgorithmException | InvalidKeyException |
                 XmlParserException | InternalException | ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public InputStream download(String fileName) {
        return download(BUCKET_NAME, fileName);
    }

    public InputStream download(String bucketName, String fileName) {
        try {
            if (Objects.nonNull(statObject(bucketName, fileName))) {
                return minioAsyncClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build()).get();
            }
            return null;
        } catch (InsufficientDataException | IOException | NoSuchAlgorithmException | InvalidKeyException |
                 XmlParserException | InternalException | ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public String getObjectUrl(String fileName) {
        return getObjectUrl(BUCKET_NAME, fileName);
    }

    public String getObjectUrl(String bucketName, String fileName) {
        String presignedObjectUrl = getPresignedObjectUrl(bucketName, fileName);
        return presignedObjectUrl.substring(0, presignedObjectUrl.lastIndexOf("?"));
    }

    public String getPresignedObjectUrl(String fileName) {
        return getPresignedObjectUrl(BUCKET_NAME, fileName);
    }

    public String getPresignedObjectUrl(String bucketName, String fileName) {
        try {
            if (Objects.nonNull(statObject(bucketName, fileName))) {
                return minioAsyncClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .method(GET)
                        .bucket(bucketName)
                        .object(fileName)
                        .build()
                );
            }
            return "";
        } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException |
                 InvalidResponseException | IOException | NoSuchAlgorithmException | XmlParserException |
                 ServerException e) {
            throw new RuntimeException(e);
        }
    }

    public void remove(String fileName) {
        remove(BUCKET_NAME, fileName);
    }

    public void remove(String bucketName, String fileName) {
        try {
            if (Objects.nonNull(statObject(bucketName, fileName))) {
                minioAsyncClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build()).get();
            }
        } catch (InsufficientDataException | IOException | NoSuchAlgorithmException | InvalidKeyException |
                 XmlParserException | InternalException | ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public StatObjectResponse statObject(String fileName) {
        return statObject(BUCKET_NAME, fileName);
    }

    public StatObjectResponse statObject(String bucketName, String fileName) {
        try {
            return minioAsyncClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build()).get();
        } catch (InsufficientDataException | InternalException | InvalidKeyException | IOException |
                 NoSuchAlgorithmException | XmlParserException | ExecutionException | InterruptedException e) {
            return null; // 不存在
        }
    }

    // ==================================================Multipart======================================================

    public Map<String, Object> createMultipartUpload(String objectName, String contentType, Integer partTotal) {
        return createMultipartUpload(BUCKET_NAME, objectName, contentType, partTotal);
    }

    public Map<String, Object> createMultipartUpload(String bucketName, String objectName, String contentType, Integer partTotal) {
        Map<String, Object> result = CollectionUtils.newHashMap(2);
        List<String> partUrlList = new ArrayList<>();
        result.put("partUrls", partUrlList);
        try {
            Multimap<String, String> headers = HashMultimap.create();
            headers.put("Content-Type", contentType);
            CreateMultipartUploadResponse createMultipartUploadResponse = minioAsyncClient.createMultipartUploadAsync(
                    bucketName, null, objectName, headers, null).get();
            String uploadId = createMultipartUploadResponse.result().uploadId();
            result.put("uploadId", uploadId);

            Map<String, String> queryParams = CollectionUtils.newHashMap(2);
            queryParams.put("uploadId", uploadId);
            for (int partNumber = 1; partNumber <= partTotal; partNumber++) {
                queryParams.put("partNumber", String.valueOf(partNumber));
                String presignedObjectUrl = minioAsyncClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                        .method(PUT)
                        .bucket(bucketName)
                        .object(objectName)
                        .extraQueryParams(queryParams)
                        .build()
                );
                partUrlList.add(presignedObjectUrl);
            }
        } catch (InterruptedException | ExecutionException | InsufficientDataException | IOException |
                 NoSuchAlgorithmException | InvalidKeyException | XmlParserException | InternalException |
                 ServerException | ErrorResponseException | InvalidResponseException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    public ObjectWriteResponse completeMultipartUpload(String objectName, String uploadId) {
        return completeMultipartUpload(BUCKET_NAME, objectName, uploadId);
    }

    public ObjectWriteResponse completeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            ListPartsResponse listPartsResponse = minioAsyncClient.listPartsAsync(bucketName, null, objectName,
                    1000, 0, uploadId, null, null).get();
            Part[] parts = listPartsResponse.result().partList().toArray(new Part[0]);

            return minioAsyncClient.completeMultipartUploadAsync(bucketName, null, objectName, uploadId,
                    parts, null, null).get();
        } catch (InterruptedException | ExecutionException | InsufficientDataException | IOException |
                 NoSuchAlgorithmException | InvalidKeyException | XmlParserException | InternalException e) {
            throw new RuntimeException(e);
        }
    }
}